package me.softwareengineer.guice.rendering.domain;

import java.security.InvalidParameterException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import me.softwareengineer.guice.rendering.domain.search.ElementFilter;
import me.softwareengineer.guice.rendering.domain.search.ElementMatcher;
import me.softwareengineer.guice.rendering.domain.search.SearchAxes;
import me.softwareengineer.guice.rendering.listener.Listener;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

/**
 * Represents an element in DOM tree. For instance, element can be a paragraph
 * or list. Element has a parent. Element can be classified into two groups:
 * wrapper elements and non-wrapper. Wrapper elements have one or more children.
 * For instance, Table or List. Non Wrapper elements do not maintain any
 * children and they are inline elements. For instance, {@link WordElement}
 * 
 * <p>
 * Each element can have one or more property. property is used to store addition information
 * in the elements. For instance, XHTML render uses property to store the tag
 * attributes. For more info, check {@link HasProperties}
 * </p>
 * 
 * <p>
 * Each element can have one or more metadata information. metadata is used to store supplementary information
 * in the elements. 
 * For example, the backup version of the element so that the parser can rerender the content of 
 * the element again. For more info, see {@link HasMetaData}
 * </p>
 * 
 * @author yadtahir
 * @see Element
 * 
 * <pre>
 * This class is thread-safe and can be cached/re-used and used concurrently.
 * </pre>
 */
public abstract class Element implements HasProperties, HasChildren, Cloneable, HasMetaData {

	/**
	 * Stores properties records of the element. See {@link HasProperties}
	 * 
	 * <pre>
	 * Note: We use concurrent version of map to support concurrency and multi-threading.
	 * For more information, check {@link ConcurrentHashMap}
	 * </pre>
	 */
	private Map<String, String> properties = new java.util.concurrent.ConcurrentHashMap<String, String>();

	/**
	 * Stores the children of the element. See {@link HasChildren}
	 * 
	 * <pre>
	 * Note: We use concurrent version of list to support concurrency and multi-threading
	 * For more information, please check {@link CopyOnWriteArrayList}
	 * </pre>
	 */
	private List<Element> children = new java.util.concurrent.CopyOnWriteArrayList<Element>();

	/**
	 * Holds the parent element of the current element.
	 */
	private Element parnetElement;

	/**
	 * Holds the next sibling element of the current element.
	 */
	private Element nextElement;

	/**
	 * Holds the previous sibling element of the current element
	 */
	private Element previousElement;

	/**
	 * Holds metadata information
	 */
	private Map<String, Object> metadata = new java.util.concurrent.ConcurrentHashMap<String, Object>();

	/**
	 * Empty constructor to construct empty element
	 */
	public Element() {
		// DO NOTHING
	}

	/**
	 * Constructs an element and adds the provided child
	 * 
	 * @param child
	 *            the child of the new element
	 */
	public Element(Element child) {
		addChild(child);
	}

	/**
	 * Creates an element and adds the provided child to the children of the
	 * newly created element.
	 * 
	 * @param child
	 *            the child of the element
	 * @param properties
	 *            the properties of the element
	 * @see HasProperties
	 */
	public Element(Element child, Map<String, String> properties) {
		this(properties);
		addChild(child);
	}

	/**
	 * Creates an element and adds the given children to the element
	 * 
	 * @param children
	 */
	public Element(List<Element> children) {
		addChildren(children);
	}

	/**
	 * Creates an element and adds the given children to the element.
	 * 
	 * @param children
	 *            the children to be added to the newly created element
	 * @param properties
	 *            the properties of the element.
	 * @see HasProperties
	 */
	public Element(List<Element> children,
			Map<String, String> properties) {
		this(properties);
		addChildren(children);
	}

	/**
	 * Constructs empty element with the provided properties
	 * 
	 * @param properties
	 * @see HasProperties
	 */
	public Element(Map<String, String> properties) {
		// Because the properties is a Concurrent map, we don't need a
		// synchronized block
		this.properties.putAll(properties);

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addChild(Element elementToAdd) {
		insertChildAfter(elementToAdd, null);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addChildren(List<Element> elementsToAdd) {
		for (Element e : elementsToAdd) {
			addChild(e);
		}
	}

	/**
	 * Sends {@link Event}s to notify about the ending of the element 
	 * 
	 * @param listener
	 *            the listener of the events.
	 */
	protected void afterElement(Listener listener) {
		// DO NOTHING. This method should be override in sub classes
	}

	/**
	 * Sends {@link Event}s to notify about the element 
	 * 
	 * @param listener
	 *            the listener of the events.
	 */
	protected void beforeElement(Listener listener) {
		// DO NOTHING. This method should be override in sub classes
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see Element#clone()
	 */
	@Override
	public Element clone() {
		//Clone without filter
		return clone(null);
	}

	/**
	 * Clones the current element and filter the children corresponding to the
	 * provided ElementFilter
	 * 
	 * @param filter
	 *            the filter of the children
	 * @return Cloned element
	 * @see ElementFilter
	 */

	public Element clone(ElementFilter blockFilter) {
		// Clone this
		Element element;
		try {
			element = (Element) super.clone();
		} catch (CloneNotSupportedException e) {
			// Should never happen
			throw new RuntimeException("Failed to clone object", e);
		}
		// Clone properties list
		((Element) element).properties = new ConcurrentHashMap<String, String>(
				this.properties);
		// Clone the children
		((Element) element).children = new CopyOnWriteArrayList<Element>();

		for (Element childBlock : this.children) {
			// Filter the children
			if (blockFilter != null) {
				Element clonedChildBlocks = childBlock.clone(blockFilter);

				List<Element> filteredBlocks = blockFilter
						.filter(clonedChildBlocks);

				if (filteredBlocks.size() == 0) {
					filteredBlocks = clonedChildBlocks.getChildren();
				}

				element.addChildren(filteredBlocks);
			} else {
				element.addChild(childBlock.clone());
			}
		}

		return element;
	}

	@Override
	public boolean equals(Object obj) {
		return EqualsBuilder.reflectionEquals(this, obj);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Map<String, String> getAllProperties() {
		// Because the properties is a Concurrent map, we don't need a
		// synchronized block
		return Collections.unmodifiableMap(properties);

	}

	/**
	 * Get all elements following provided {@link ElementMatcher} and
	 * {@link SearchAxes}.
	 * 
	 * @param matcher
	 *            filter the elements to return
	 * @param axes
	 *            indicate the search axes
	 * @return the matched {@link Element}s, empty list of none was found
	 * @param matcher
	 * @param axes
	 */
	public List<Element> getBlocks(ElementMatcher matcher, SearchAxes axes) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<Element> getChildren() {
		// Because the properties is a Concurrent list, we don't need a
		// synchronized block
		return Collections.unmodifiableList(this.children);
	}


	/**
	 * Get the first matched element in the provided {@link SearchAxes}.
	 * 
	 * @param matcher
	 *            indicate which block to stop to
	 * @param axes
	 *            indicate the search axes
	 * @return the matched {@link Element}, null if none was found
	 */
	public Element getFirstBlock(ElementMatcher matcher, SearchAxes axes) {
		// TODO Auto-generated method stub
		return null;
	}


	/**
	 * @return the next sibling Element or null if there's no next sibling
	 * <p>
	 * This property is immutable and thread-safe
	 * </p>
	 */
	public Element getNextSibling() {
			return this.nextElement;
		
	}

	/**
	 * Get the parent element. All element have a parent and the top level
	 * parent is the {@link DOM} object.
	 * 
	 * @return the parent block
	 * <p>
	 * This property is immutable and thread-safe
	 * </p>
	 */
	public Element getParent() {
				return this.parnetElement;
		
	}

	/**
	 * @return the previous sibling Element or null if there's no previous
	 *         sibling
	 *  <p>
	 * This property is immutable and thread-safe
	 * </p>
	 */
	public Element getPreviousSibling() {
			return this.previousElement;
		
	}

	
	@Override
	public String getProperty(String propertiesName) {
		synchronized (properties) {
			if (properties.containsKey(propertiesName)) {
				return properties.get(propertiesName);
			}
		}
		
		return null;

	}

	/**
	 * Gets the top level element. If the current block is {@link DOM}, it
	 * return itself.
	 * 
	 * @return the top level Block
	 */
	public Element getRoot() {
		Element e = this;

		while (e.getParent() != null) {
			e = e.getParent();
		}
		return e;
	}

	@Override
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this);
	}

	/**
	 * Get the position of the provided block in the provided list of blocks.
	 * <p>
	 * Can't use {@link List#indexOf(Object)} since it's using
	 * {@link Object#equals(Object)} internally which is not what we want since
	 * two WordBlock with the same text or two spaces are equals for example but
	 * we want to be able to target one specific Block.
	 * 
	 * @param block
	 *            the block for which to find the position
	 * @param blocks
	 *            the list of blocks in which to look for the passed block
	 * @return the position of the block, -1 if the block can't be found
	 */
	private int indexOfBlock(Element block, List<Element> blocks) {
		int position = 0;

		for (Element child : blocks) {
			if (child == block) {
				return position;
			}
			++position;
		}

		return -1;
	}

	/**
	 * Get the position of the provided element in the list of children.
	 * <p>
	 * Can't use {@link List#indexOf(Object)} since it's using
	 * {@link Object#equals(Object)} internally which is not what we want since
	 * two WordBlock with the same text or two spaces are equals for example but
	 * we want to be able to target one specific Block.
	 * 
	 * @param block
	 *            the block
	 * @return the position of the block, -1 if the block can't be found
	 */
	private int indexOfChild(Element block) {
		return indexOfBlock(block, this.children);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void insertChildAfter(Element elementToInsert,
			Element existingElement) {
	
		
		if (existingElement == null)
			// Add to the end
			insertChildBefore(elementToInsert, null);
		else {
			//lock existing element
			synchronized (existingElement) {
				
				// Get the next sibling
				Element nextSibling = existingElement.getNextSibling();
				
				
				if (nextSibling != null) {
					//lock the next element 
					synchronized (nextSibling) {
						// Change the previous
						nextSibling.setPreviousSiblingElement(elementToInsert);
					}
				}
				//lock the new element
				synchronized (elementToInsert) {
					// Set the next
					elementToInsert.setNextSiblingElement(nextSibling);
					// Set the previous
					elementToInsert.setPreviousSiblingElement(existingElement);
					// Adjust the exsiting
					existingElement.setNextSiblingElement(elementToInsert);
				}
				
				// Since we have two operation on the list, we put a synchronized
				// block
				synchronized (children) {
					this.children.add(indexOfChild(existingElement) + 1,
							elementToInsert);
				}
			}
			
		}

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void insertChildBefore(final Element elementToInsert,
			final Element existingElement) {
		//prepare the new element
		synchronized (elementToInsert) {
			// Set the parent of the new child element
			elementToInsert.setParent(this);
	
			if (existingElement == null) {
				// If existing element does not exist
	
				// Since we have two operation on the list, we put a synchronized
				// block
				synchronized (children) {
					// Check the children list
					if (!this.children.isEmpty()) {
	
						Element lastElement = this.children.get(this.children
								.size() - 1);
						synchronized (lastElement) {
							lastElement.setNextSiblingElement(elementToInsert);
							
						}
						elementToInsert.setPreviousSiblingElement(lastElement);
					} else {
						// Set it is the first child element
						elementToInsert.setPreviousSiblingElement(null);
					}
					elementToInsert.setNextSiblingElement(null);
	
					this.children.add(elementToInsert);
				}
			} else {
				synchronized (existingElement) {
					
				
				// Find the pervious sibling element of the given element
				Element perviousElement = existingElement.getPreviousSibling();
					
					// Change the next element
					if (perviousElement != null) {
						//prepare previous element modification
						synchronized (perviousElement) {
							perviousElement.setNextSiblingElement(elementToInsert);
						}
					}
				
					// Set the new previous element
					elementToInsert.setPreviousSiblingElement(perviousElement);
					elementToInsert.setNextSiblingElement(existingElement);
				
				existingElement.setPreviousSiblingElement(elementToInsert);
				// Since we have two operation on the list, we put a synchronized
				// block
				synchronized (children) {
					this.children.add(indexOfChild(existingElement),
							elementToInsert);
				}
				}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean propertyExist(String name) {
		return properties.containsKey(name);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeElement(Element childElementToRemove) {
		// First remove it from the children list
		// Because the properties is a Concurrent list, we don't need a
		// synchronized block
		
		this.children.remove(childElementToRemove);
		// Adjust the siblings
		synchronized (childElementToRemove) {
		
				Element previousElement = childElementToRemove.getPreviousSibling();
				Element nextElement = childElementToRemove.getNextSibling();

				if (previousElement != null)
					synchronized (previousElement) {
						previousElement.setNextSiblingElement(nextElement);
					}
					
				if (nextElement != null)
					synchronized (nextElement) {
						nextElement.setPreviousSiblingElement(previousElement);
					}
				childElementToRemove.setNextSiblingElement(null);
				childElementToRemove.setPreviousSiblingElement(null);
			
		}
		

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeProperty(String name) {
		synchronized (properties) {
			if (properties.containsKey(name))
				this.properties.remove(name);
		}
		
	}

	/**
	 * Renders the element and sends the event occurred to the provided
	 * listener.
	 * 
	 * @param listener
	 *            the listener of the element events
	 * @see Listener
	 * @see Event
	 */
	public void render(Listener listener) {
		// Call before element events
		beforeElement(listener);

		// Render the children
		for (Element e : getChildren()) {
			e.render(listener);
		}

		// Call after element events
		afterElement(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void replaceChild(Element existingChild, Element newChild) {
		replaceChild(existingChild, Collections.singletonList(newChild));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	// Since we have couple of operation working on the children, we added
	// synchronized keyword
	// to the definition of the method
	public synchronized void replaceChild(Element existingChild,
			List<? extends Element> newElements) {
		synchronized (existingChild) {
			synchronized (children) {
				
			
			// Check if existing child does exist in the children list
			int index = indexOfChild(existingChild);
			if (index == -1)
				throw new InvalidParameterException(
						"Provided Element to replace is not a valid child");
			
	
			// Remove the old element from the children
			children.remove(index);
			Element previousElement = null;
			synchronized (newElements) {
				// Add the new element
				
				previousElement = existingChild.getPreviousSibling();
					
					// if there is no new element. For instance in remove use case
					if (newElements.isEmpty()) {
						if(previousElement != null)
							synchronized (previousElement) {
								previousElement.setNextSiblingElement(nextElement);
							}
					}
		
					for (Element newElement : newElements) {
						synchronized (newElement) {
								newElement.setParent(this);
								newElement.setPreviousSiblingElement(previousElement);
								if (previousElement != null)
									synchronized (previousElement){
										previousElement.setNextSiblingElement(newElement);
									}
								previousElement = newElement;
							}
							
						}
						
				}
			
				// Find the next element of the old child
				Element nextElement = existingChild.getNextSibling();
				// Adjust the previous sibling of next element
				if (nextElement != null)
					synchronized (nextElement) {
						// Last element in the list
						nextElement.setPreviousSiblingElement(previousElement);
					}
				
				if (previousElement != null)
					synchronized (previousElement) {
						previousElement.setNextSiblingElement(nextElement);
					}
				// Add to children list
				children.addAll(index, newElements);
			}
				// Remove all the relationship
				existingChild.setParent(null);
				existingChild.setPreviousSiblingElement(null);
				existingChild.setNextSiblingElement(null);
			}
		

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setChildren(List<Element> elementsToReplace) {
		// Since we have two operations working on the list, It is better
		// to lock the children object
		synchronized (children) {
			// Clear the current children
			this.children.clear();

			// Adds the new children
			addChildren(elementsToReplace);
		}

	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see Element#setNextSiblingElement(Element)
	 */
	
	private void setNextSiblingElement(Element nextSiblingBlock) {
				this.nextElement = nextSiblingBlock;
		

	}

	/**
	 * Sets the parent of the element.
	 * <p>
	 * This property is immutable and this method should have private modifier
	 * </p>
	 * @param parentElement
	 */
	private void setParent(Element parentElement) {
			this.parnetElement = parentElement;
	}

	/**
	 * Sets the previous element
	 * <p>
	 * This property is immutable and this method should have private modifier
	 * </p>
	 * 
	 */
	
	private void setPreviousSiblingElement(Element previousSiblingBlock) {
		
				this.previousElement = previousSiblingBlock;
		
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setProperties(Map<String, String> propertiesRecords) {
		properties.putAll(propertiesRecords);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setProperty(String name, String value) {
		properties.put(name, value);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addMetaData(Map<String, Object> records) {
		this.metadata.putAll(records);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addMetaData(String key, Object value) {
		this.metadata.put(key, value);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean containsMetadata(String key) {
		return this.metadata.containsKey(key);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Map<String, Object> getAllMetaData() {
		return Collections.unmodifiableMap(this.metadata);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getMetaData(String key) {
		return this.metadata.get(key);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeMetaData(String key) {
		// Since we are doing more than one operation, we need to use a sync
		// block
		synchronized (metadata) {
			if (containsMetadata(key))
				this.metadata.remove(key);
		}

	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addMetaData(HasMetaData metaData) {
		synchronized (metaData) {
			for (Map.Entry<String, Object> entry : metaData.getAllMetaData()
				.entrySet()) {
			addMetaData(entry.getKey(), entry.getValue());
		}
		}
		
	}


}
